// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/ime/input_method_auralinux.h"

#include <stddef.h>

#include "base/macros.h"
#include "base/memory/singleton.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/dummy_text_input_client.h"
#include "ui/base/ime/input_method_delegate.h"
#include "ui/base/ime/input_method_initializer.h"
#include "ui/base/ime/linux/fake_input_method_context.h"
#include "ui/base/ime/linux/linux_input_method_context_factory.h"
#include "ui/events/event.h"

namespace ui {
namespace {

    const base::char16 kActionCommit = L'C';
    const base::char16 kActionCompositionStart = L'S';
    const base::char16 kActionCompositionUpdate = L'U';
    const base::char16 kActionCompositionEnd = L'E';

    class TestResult {
    public:
        static TestResult* GetInstance()
        {
            return base::Singleton<TestResult>::get();
        }

        void RecordAction(const base::string16& action)
        {
            recorded_actions_.push_back(action);
        }

        void ExpectAction(const std::string& action)
        {
            expected_actions_.push_back(base::ASCIIToUTF16(action));
        }

        void Verify()
        {
            size_t len = recorded_actions_.size();
            size_t len_exp = expected_actions_.size();
            EXPECT_EQ(len_exp, len);
            for (size_t i = 0; i < len; i++)
                EXPECT_EQ(expected_actions_[i], recorded_actions_[i]);
            recorded_actions_.clear();
            expected_actions_.clear();
        }

    private:
        std::vector<base::string16> recorded_actions_;
        std::vector<base::string16> expected_actions_;
    };

    class LinuxInputMethodContextForTesting : public LinuxInputMethodContext {
    public:
        LinuxInputMethodContextForTesting(LinuxInputMethodContextDelegate* delegate,
            bool is_simple)
            : delegate_(delegate)
            , is_simple_(is_simple)
            , is_sync_mode_(false)
            , eat_key_(false)
            , focused_(false)
        {
        }

        void SetSyncMode(bool is_sync_mode) { is_sync_mode_ = is_sync_mode; }
        void SetEatKey(bool eat_key) { eat_key_ = eat_key; }

        void AddCommitAction(const std::string& text)
        {
            actions_.push_back(base::ASCIIToUTF16("C:" + text));
        }

        void AddCompositionUpdateAction(const std::string& text)
        {
            actions_.push_back(base::ASCIIToUTF16("U:" + text));
        }

        void AddCompositionStartAction()
        {
            actions_.push_back(base::ASCIIToUTF16("S"));
        }

        void AddCompositionEndAction()
        {
            actions_.push_back(base::ASCIIToUTF16("E"));
        }

    protected:
        bool DispatchKeyEvent(const ui::KeyEvent& key_event) override
        {
            if (!is_sync_mode_) {
                actions_.clear();
                return eat_key_;
            }

            for (const auto& action : actions_) {
                std::vector<base::string16> parts = base::SplitString(
                    action, base::string16(1, ':'), base::TRIM_WHITESPACE,
                    base::SPLIT_WANT_ALL);
                base::char16 id = parts[0][0];
                base::string16 param;
                if (parts.size() > 1)
                    param = parts[1];
                if (id == kActionCommit) {
                    delegate_->OnCommit(param);
                } else if (id == kActionCompositionStart) {
                    delegate_->OnPreeditStart();
                } else if (id == kActionCompositionUpdate) {
                    CompositionText comp;
                    comp.text = param;
                    delegate_->OnPreeditChanged(comp);
                } else if (id == kActionCompositionEnd) {
                    delegate_->OnPreeditEnd();
                }
            }

            actions_.clear();
            return eat_key_;
        }

        void Reset() override { }

        void Focus() override { focused_ = true; }

        void Blur() override { focused_ = false; }

        void SetCursorLocation(const gfx::Rect& rect) override
        {
            cursor_position_ = rect;
        }

    private:
        LinuxInputMethodContextDelegate* delegate_;
        std::vector<base::string16> actions_;
        bool is_simple_;
        bool is_sync_mode_;
        bool eat_key_;
        bool focused_;
        gfx::Rect cursor_position_;

        DISALLOW_COPY_AND_ASSIGN(LinuxInputMethodContextForTesting);
    };

    class LinuxInputMethodContextFactoryForTesting
        : public LinuxInputMethodContextFactory {
    public:
        LinuxInputMethodContextFactoryForTesting() {};

        scoped_ptr<LinuxInputMethodContext> CreateInputMethodContext(
            LinuxInputMethodContextDelegate* delegate,
            bool is_simple) const override
        {
            return scoped_ptr<ui::LinuxInputMethodContext>(
                new LinuxInputMethodContextForTesting(delegate, is_simple));
        };

    private:
        DISALLOW_COPY_AND_ASSIGN(LinuxInputMethodContextFactoryForTesting);
    };

    class InputMethodDelegateForTesting : public internal::InputMethodDelegate {
    public:
        InputMethodDelegateForTesting() {};
        ~InputMethodDelegateForTesting() override {};

        ui::EventDispatchDetails DispatchKeyEventPostIME(
            ui::KeyEvent* key_event) override
        {
            std::string action;
            switch (key_event->type()) {
            case ET_KEY_PRESSED:
                action = "keydown:";
                break;
            case ET_KEY_RELEASED:
                action = "keyup:";
                break;
            default:
                break;
            }
            std::stringstream ss;
            ss << key_event->key_code();
            action += std::string(ss.str());
            TestResult::GetInstance()->RecordAction(base::ASCIIToUTF16(action));
            return ui::EventDispatchDetails();
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(InputMethodDelegateForTesting);
    };

    class TextInputClientForTesting : public DummyTextInputClient {
    public:
        explicit TextInputClientForTesting(TextInputType text_input_type)
            : DummyTextInputClient(text_input_type) {};

        base::string16 composition_text;

    protected:
        void SetCompositionText(const CompositionText& composition) override
        {
            composition_text = composition.text;
            TestResult::GetInstance()->RecordAction(
                base::ASCIIToUTF16("compositionstart"));
            TestResult::GetInstance()->RecordAction(
                base::ASCIIToUTF16("compositionupdate:") + composition.text);
        }

        bool HasCompositionText() const override { return !composition_text.empty(); }

        void ConfirmCompositionText() override
        {
            TestResult::GetInstance()->RecordAction(
                base::ASCIIToUTF16("compositionend"));
            TestResult::GetInstance()->RecordAction(base::ASCIIToUTF16("textinput:") + composition_text);
            composition_text.clear();
        }

        void ClearCompositionText() override
        {
            TestResult::GetInstance()->RecordAction(
                base::ASCIIToUTF16("compositionend"));
            composition_text.clear();
        }

        void InsertText(const base::string16& text) override
        {
            if (HasCompositionText()) {
                TestResult::GetInstance()->RecordAction(
                    base::ASCIIToUTF16("compositionend"));
            }
            TestResult::GetInstance()->RecordAction(base::ASCIIToUTF16("textinput:") + text);
            composition_text.clear();
        }

        void InsertChar(const ui::KeyEvent& event) override
        {
            std::stringstream ss;
            ss << event.GetCharacter();
            TestResult::GetInstance()->RecordAction(base::ASCIIToUTF16("keypress:") + base::ASCIIToUTF16(ss.str()));
        }
    };

    class InputMethodAuraLinuxTest : public testing::Test {
    protected:
        InputMethodAuraLinuxTest()
            : factory_(NULL)
            , input_method_auralinux_(NULL)
            , delegate_(NULL)
            , context_(NULL)
            , context_simple_(NULL)
        {
            factory_ = new LinuxInputMethodContextFactoryForTesting();
            LinuxInputMethodContextFactory::SetInstance(factory_);
            test_result_ = TestResult::GetInstance();
        }
        ~InputMethodAuraLinuxTest() override
        {
            delete factory_;
            factory_ = NULL;
            test_result_ = NULL;
        }

        void SetUp() override
        {
            delegate_ = new InputMethodDelegateForTesting();
            input_method_auralinux_ = new InputMethodAuraLinux(delegate_);
            input_method_auralinux_->OnFocus();
            context_ = static_cast<LinuxInputMethodContextForTesting*>(
                input_method_auralinux_->GetContextForTesting(false));
            context_simple_ = static_cast<LinuxInputMethodContextForTesting*>(
                input_method_auralinux_->GetContextForTesting(true));
        }

        void TearDown() override
        {
            context_->SetSyncMode(false);
            context_->SetEatKey(false);

            context_simple_->SetSyncMode(false);
            context_simple_->SetEatKey(false);

            context_ = NULL;
            context_simple_ = NULL;

            delete input_method_auralinux_;
            input_method_auralinux_ = NULL;
            delete delegate_;
            delegate_ = NULL;
        }

        LinuxInputMethodContextFactoryForTesting* factory_;
        InputMethodAuraLinux* input_method_auralinux_;
        InputMethodDelegateForTesting* delegate_;
        LinuxInputMethodContextForTesting* context_;
        LinuxInputMethodContextForTesting* context_simple_;
        TestResult* test_result_;

        DISALLOW_COPY_AND_ASSIGN(InputMethodAuraLinuxTest);
    };

    TEST_F(InputMethodAuraLinuxTest, BasicSyncModeTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCommitAction("a");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');

        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:97");
        test_result_->Verify();

        input_method_auralinux_->DetachTextInputClient(client.get());
        client.reset(new TextInputClientForTesting(TEXT_INPUT_TYPE_PASSWORD));
        context_simple_->SetSyncMode(true);
        context_simple_->SetEatKey(false);

        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:97");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, BasicAsyncModeTest)
    {
        context_->SetSyncMode(false);
        context_->SetEatKey(true);

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());
        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);
        input_method_auralinux_->OnCommit(base::ASCIIToUTF16("a"));

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("textinput:a");
        test_result_->Verify();

        input_method_auralinux_->DetachTextInputClient(client.get());
        client.reset(new TextInputClientForTesting(TEXT_INPUT_TYPE_PASSWORD));
        context_simple_->SetSyncMode(false);
        context_simple_->SetEatKey(false);

        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:97");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, IBusUSTest)
    {
        context_->SetSyncMode(false);
        context_->SetEatKey(true);

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());
        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        // IBus mutes the key down.
        test_result_->Verify();

        // IBus simulates a faked key down and handle it in sync mode.
        context_->SetSyncMode(true);
        context_->AddCommitAction("a");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:97");
        test_result_->Verify();

        // IBus does NOT handle the key up.
        context_->SetEatKey(false);
        KeyEvent key_up(ET_KEY_RELEASED, VKEY_A, 0);
        input_method_auralinux_->DispatchKeyEvent(&key_up);

        test_result_->ExpectAction("keyup:65");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, IBusPinyinTest)
    {
        context_->SetSyncMode(false);
        context_->SetEatKey(true);

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());
        KeyEvent key(ET_KEY_PRESSED, VKEY_A, 0);
        key.set_character(L'a');
        input_method_auralinux_->DispatchKeyEvent(&key);

        // IBus issues a standalone set_composition action.
        input_method_auralinux_->OnPreeditStart();
        CompositionText comp;
        comp.text = base::ASCIIToUTF16("a");
        input_method_auralinux_->OnPreeditChanged(comp);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        // IBus issues a commit text with composition after muting the space key down.
        KeyEvent key_up(ET_KEY_RELEASED, VKEY_SPACE, 0);
        input_method_auralinux_->DispatchKeyEvent(&key_up);

        input_method_auralinux_->OnPreeditEnd();
        input_method_auralinux_->OnCommit(base::ASCIIToUTF16("A"));

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("textinput:A");
        test_result_->Verify();
    }

    // crbug.com/463491
    TEST_F(InputMethodAuraLinuxTest, DeadKeyTest)
    {
        context_simple_->SetSyncMode(true);
        context_simple_->SetEatKey(true);

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_NONE));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent dead_key(ET_KEY_PRESSED, VKEY_OEM_7, 0);
        dead_key.set_character(L'\'');
        input_method_auralinux_->DispatchKeyEvent(&dead_key);

        // The single quote key is muted.
        test_result_->ExpectAction("keydown:222");
        test_result_->Verify();

        context_simple_->AddCommitAction("X");
        KeyEvent key(ET_KEY_PRESSED, VKEY_A, 0);
        key.set_character(L'a');
        input_method_auralinux_->DispatchKeyEvent(&key);

        // The following A key generates the accent key: á.
        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:88");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, MultiCommitsTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCommitAction("a");
        context_->AddCommitAction("b");
        context_->AddCommitAction("c");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key(ET_KEY_PRESSED, VKEY_A, 0);
        key.set_character(L'a');
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("textinput:abc");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, MixedCompositionAndCommitTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCommitAction("a");
        context_->AddCompositionStartAction();
        context_->AddCompositionUpdateAction("b");
        context_->AddCommitAction("c");
        context_->AddCompositionUpdateAction("d");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("textinput:ac");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:d");
        test_result_->Verify();

        context_->AddCommitAction("e");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("textinput:e");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, CompositionEndWithoutCommitTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCompositionStartAction();
        context_->AddCompositionUpdateAction("a");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        context_->AddCompositionEndAction();
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, CompositionEndWithEmptyCommitTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCompositionStartAction();
        context_->AddCompositionUpdateAction("a");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        context_->AddCompositionEndAction();
        context_->AddCommitAction("");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, CompositionEndWithCommitTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCompositionStartAction();
        context_->AddCompositionUpdateAction("a");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        context_->AddCompositionEndAction();
        context_->AddCommitAction("b");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        // Verifies single char commit under composition mode will call InsertText
        // intead of InsertChar.
        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("textinput:b");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, CompositionUpdateWithCommitTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCompositionStartAction();
        context_->AddCompositionUpdateAction("a");
        context_->AddCommitAction("b");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("textinput:b");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        // crbug.com/513124.
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCommitAction("c");
        context_->AddCompositionUpdateAction("");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("textinput:c");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, MixedAsyncAndSyncTest)
    {
        context_->SetSyncMode(false);
        context_->SetEatKey(true);

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);
        CompositionText comp;
        comp.text = base::ASCIIToUTF16("a");
        input_method_auralinux_->OnPreeditChanged(comp);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        context_->SetSyncMode(true);
        context_->AddCompositionEndAction();
        context_->AddCommitAction("b");

        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("textinput:b");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, MixedSyncAndAsyncTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCompositionStartAction();
        context_->AddCompositionUpdateAction("a");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'a');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        context_->SetSyncMode(false);

        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);
        input_method_auralinux_->OnCommit(base::ASCIIToUTF16("b"));

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("textinput:b");
        test_result_->Verify();

        context_->SetSyncMode(true);
        context_->AddCommitAction("c");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:99");
        test_result_->Verify();
    }

    TEST_F(InputMethodAuraLinuxTest, ReleaseKeyTest)
    {
        context_->SetSyncMode(true);
        context_->SetEatKey(true);
        context_->AddCompositionUpdateAction("a");

        scoped_ptr<TextInputClientForTesting> client(
            new TextInputClientForTesting(TEXT_INPUT_TYPE_TEXT));
        input_method_auralinux_->SetFocusedTextInputClient(client.get());
        input_method_auralinux_->OnTextInputTypeChanged(client.get());

        KeyEvent key_new(ET_KEY_PRESSED, VKEY_A, 0);
        key_new.set_character(L'A');
        KeyEvent key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("keydown:229");
        test_result_->ExpectAction("compositionstart");
        test_result_->ExpectAction("compositionupdate:a");
        test_result_->Verify();

        context_->SetEatKey(false);
        context_->AddCommitAction("b");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("compositionend");
        test_result_->ExpectAction("textinput:b");
        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:65");
        test_result_->Verify();

        context_->AddCommitAction("c");
        key = key_new;
        input_method_auralinux_->DispatchKeyEvent(&key);

        test_result_->ExpectAction("textinput:c");
        test_result_->ExpectAction("keydown:65");
        test_result_->ExpectAction("keypress:65");
        test_result_->Verify();
    }

} // namespace
} // namespace ui
